// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/views/search_result_page_view.h"

#include <stddef.h>

#include <algorithm>

#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_switches.h"
#include "ui/app_list/app_list_view_delegate.h"
#include "ui/app_list/views/app_list_main_view.h"
#include "ui/app_list/views/search_box_view.h"
#include "ui/app_list/views/search_result_list_view.h"
#include "ui/app_list/views/search_result_tile_item_list_view.h"
#include "ui/gfx/shadow_value.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/shadow_border.h"

namespace app_list {

namespace {

    const int kGroupSpacing = 6;
    const int kTopPadding = 8;

    // The z-height of the search box and cards in this view.
    const int kSearchResultZHeight = 1;

    // A container view that ensures the card background and the shadow are painted
    // in the correct order.
    class SearchCardView : public views::View {
    public:
        explicit SearchCardView(views::View* content_view)
        {
            SetBorder(make_scoped_ptr(
                new views::ShadowBorder(GetShadowForZHeight(kSearchResultZHeight))));
            SetLayoutManager(new views::FillLayout());
            content_view->set_background(
                views::Background::CreateSolidBackground(kCardBackgroundColor));
            AddChildView(content_view);
        }

        ~SearchCardView() override { }
    };

} // namespace

SearchResultPageView::SearchResultPageView()
    : selected_index_(0)
{
    if (switches::IsExperimentalAppListEnabled()) {
        gfx::ShadowValue shadow = GetShadowForZHeight(kSearchResultZHeight);
        scoped_ptr<views::Border> border(new views::ShadowBorder(shadow));

        gfx::Insets insets = gfx::Insets(kTopPadding, kExperimentalSearchBoxPadding,
            0, kExperimentalSearchBoxPadding);
        insets += -border->GetInsets();

        views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, kGroupSpacing);
        layout->set_inside_border_insets(insets);

        SetLayoutManager(layout);
    } else {
        SetLayoutManager(new views::FillLayout);
    }
}

SearchResultPageView::~SearchResultPageView()
{
}

void SearchResultPageView::SetSelection(bool select)
{
    if (select)
        SetSelectedIndex(0, false);
    else
        ClearSelectedIndex();
}

void SearchResultPageView::AddSearchResultContainerView(
    AppListModel::SearchResults* results_model,
    SearchResultContainerView* result_container)
{
    views::View* view_to_add = result_container;
    if (switches::IsExperimentalAppListEnabled())
        view_to_add = new SearchCardView(result_container);

    AddChildView(view_to_add);
    result_container_views_.push_back(result_container);
    result_container->SetResults(results_model);
    result_container->set_delegate(this);
}

bool SearchResultPageView::OnKeyPressed(const ui::KeyEvent& event)
{
    if (HasSelection() && result_container_views_.at(selected_index_)->OnKeyPressed(event)) {
        return true;
    }

    int dir = 0;
    bool directional_movement = false;
    switch (event.key_code()) {
    case ui::VKEY_TAB:
        dir = event.IsShiftDown() ? -1 : 1;
        break;
    case ui::VKEY_UP:
        dir = -1;
        directional_movement = true;
        break;
    case ui::VKEY_DOWN:
        dir = 1;
        directional_movement = true;
        break;
    default:
        return false;
    }

    // Find the next result container with results.
    int new_selected = selected_index_;
    do {
        new_selected += dir;
    } while (IsValidSelectionIndex(new_selected) && result_container_views_[new_selected]->num_results() == 0);

    if (IsValidSelectionIndex(new_selected)) {
        SetSelectedIndex(new_selected, directional_movement);
        return true;
    }

    return false;
}

void SearchResultPageView::ClearSelectedIndex()
{
    if (HasSelection())
        result_container_views_[selected_index_]->ClearSelectedIndex();

    selected_index_ = -1;
}

void SearchResultPageView::SetSelectedIndex(int index,
    bool directional_movement)
{
    bool from_bottom = index < selected_index_;

    // Reset the old selected view's selection.
    ClearSelectedIndex();

    selected_index_ = index;
    // Set the new selected view's selection to its first result.
    result_container_views_[selected_index_]->OnContainerSelected(
        from_bottom, directional_movement);
}

bool SearchResultPageView::IsValidSelectionIndex(int index)
{
    return index >= 0 && index < static_cast<int>(result_container_views_.size());
}

void SearchResultPageView::OnSearchResultContainerResultsChanged()
{
    DCHECK(!result_container_views_.empty());

    // Only sort and layout the containers when they have all updated.
    for (SearchResultContainerView* view : result_container_views_) {
        if (view->UpdateScheduled()) {
            return;
        }
    }

    SearchResultContainerView* old_selection = HasSelection() ? result_container_views_[selected_index_] : nullptr;

    // Truncate the currently selected container's selection if necessary. If
    // there are no results, the selection will be cleared below.
    if (old_selection && old_selection->num_results() > 0 && old_selection->selected_index() >= old_selection->num_results()) {
        old_selection->SetSelectedIndex(old_selection->num_results() - 1);
    }

    if (switches::IsExperimentalAppListEnabled()) {
        // Sort the result container views by their score.
        std::sort(result_container_views_.begin(), result_container_views_.end(),
            [](const SearchResultContainerView* a,
                const SearchResultContainerView* b) -> bool {
                return a->container_score() > b->container_score();
            });

        int result_y_index = 0;
        for (size_t i = 0; i < result_container_views_.size(); ++i) {
            SearchResultContainerView* view = result_container_views_[i];
            ReorderChildView(view->parent(), i);

            view->NotifyFirstResultYIndex(result_y_index);

            result_y_index += view->GetYSize();
        }
    }

    Layout();

    SearchResultContainerView* new_selection = nullptr;
    if (HasSelection() && result_container_views_[selected_index_]->num_results() > 0) {
        new_selection = result_container_views_[selected_index_];
    }

    // If there was no previous selection or the new view at the selection index
    // is different from the old one, update the selected view.
    if (!HasSelection() || old_selection != new_selection) {
        if (old_selection)
            old_selection->ClearSelectedIndex();

        int new_selection_index = new_selection ? selected_index_ : 0;
        // Clear the current selection so that the selection always comes in from
        // the top.
        ClearSelectedIndex();
        SetSelectedIndex(new_selection_index, false);
    }
}

gfx::Rect SearchResultPageView::GetPageBoundsForState(
    AppListModel::State state) const
{
    gfx::Rect onscreen_bounds = GetDefaultContentsBounds();
    switch (state) {
    case AppListModel::STATE_SEARCH_RESULTS:
        return onscreen_bounds;
    default:
        return GetAboveContentsOffscreenBounds(onscreen_bounds.size());
    }
}

void SearchResultPageView::OnAnimationUpdated(double progress,
    AppListModel::State from_state,
    AppListModel::State to_state)
{
    if (from_state != AppListModel::STATE_SEARCH_RESULTS && to_state != AppListModel::STATE_SEARCH_RESULTS) {
        return;
    }

    gfx::Rect onscreen_bounds(
        GetPageBoundsForState(AppListModel::STATE_SEARCH_RESULTS));
    set_clip_insets(bounds().InsetsFrom(onscreen_bounds));
}

int SearchResultPageView::GetSearchBoxZHeight() const
{
    return switches::IsExperimentalAppListEnabled()
        ? kSearchResultZHeight
        : AppListPage::GetSearchBoxZHeight();
}

void SearchResultPageView::OnHidden()
{
    ClearSelectedIndex();
}

} // namespace app_list
